home *** CD-ROM | disk | FTP | other *** search
/ Amiga Developer CD 2.1 / Amiga Developer CD v2.1.iso / Reference / Amiga_Mail_Vol2 / Archives / Plain / mj91 / ImageClass / imageclass.txt < prev    next >
Encoding:
Text File  |  1991-06-12  |  23.1 KB  |  497 lines

  1. (c)  Copyright 1991 Commodore-Amiga, Inc.   All rights reserved.
  2. The information contained herein is subject to change without notice,
  3. and is provided "as is" without warranty of any kind, either expressed
  4. or implied.  The entire risk as to the use of this information is
  5. assumed by the user.
  6.  
  7.  
  8. Writing a
  9. Boopsi Image Class
  10.  
  11.  
  12. By David N. Junod
  13.  
  14. Editor's note: this article and its source code reference several functions that, at press time, were only available from the classface.asm and hookface.asm assembly source files that appear on the Atlanta and Milan DevCon disks.  The functions from these files should eventually appear in amiga.lib.
  15.  
  16. The most sophisticated level of Intuition programming is to write a boopsi class.  The easiest way to enter the boopsi class writer's arena is to write an image class.  
  17.  
  18. Boopsi's imageclass is one of the standard classes built into Intuition.  As its name implies, it is a class of Intuition Images.  These boopsi images can be used in place of traditional Image structure (as they contain an Intuition Image structure), but they are much more powerful.  By using boopsi methods, an application or Intuition can tell an imageclass object to render itself.  Because it renders itself (rather than Intuition rendering it), the imageclass object is free to render whatever it wants (well, within reason).  For example, a boopsi image object can render itself according to the current display resolution, or to scale itself to any size an application requests.
  19.  
  20. This article is designed to provide the novice boopsi programmer with the information needed to write an image class for their application.  This article assumes the reader is already familar with some boopsi concepts.  For more information on boopsi, see the article ``Introduction to Boopsi'' in the March/April 1991 issue of Amiga Mail or the Atlanta (or Milan) DevCon notes and disks.  The example custom class at the end of this article, mytextlabelclass.c, shows how to create a custom image class that renders a text label with an underline beneath a character.  This character can be used to trigger some event.
  21.  
  22. When designing a specific class, you must first choose a superclass that is suitable for your needs.  If you are creating a new image class, then its superclass will either be imageclass or some subclass of imageclass.  
  23.  
  24. Classes may be public or private.  Any application can access a public class.  Before a class can be considered public it must first have a name and must be part of  the public class list maintained by Intuition.  It can then be accessed by other applications.  A private class is not in the public class list and can only be used by applications that have a pointer to the Class structure (usually the application that implemented the class).
  25.  
  26.  
  27. Callback Hooks
  28.  
  29. When you present a custom image to Intuition, you provide a pointer to a Hook structure that Intuition uses to find functions needed by various image operations.  Without going into great detail, a hook provides a pointer to a function that the system calls using Amiga register parameter conventions.  The hook supplies enough information to conveniently transfer control to a High-Level Language (HLL) entry point.  Boopsi imageclass objects provide Intuition with a hook to a method dispatcher function.
  30.  
  31. The Hook structure is defined as follows (from <utility/hooks.h>):
  32.  
  33.     /* new standard hook structure */
  34.     struct Hook
  35.     {
  36.         struct MinNode  h_MinNode;
  37.         ULONG           (*h_Entry)();   /* stub function entry point */
  38.         ULONG           (*h_SubEntry)();/* the custom function entry point */
  39.         VOID            *h_Data;        /* owner specific */
  40.     };
  41.  
  42.  
  43. The assembly language stub for C parameter conventions that boopsi classes (and custom gadgets) require is:
  44.  
  45.     _hookEntry:
  46.         move.l  a1,-(sp)                ; push message packet pointer
  47.         move.l  a2,-(sp)                ; push object pointer
  48.         move.l  a0,-(sp)                ; push hook pointer
  49.         move.l  h_SubEntry(a0),a0       ; fetch C entry point ...
  50.         jsr     (a0)                    ; ... and call it
  51.         lea     12(sp),sp               ; fix stack
  52.         rts
  53.  
  54.  
  55. The C language stub, for C compilers that support registerized parameters is:
  56.  
  57.     /* This function converts register-parameter hook calling
  58.      * convention into standard C conventions.  It requires a C
  59.      * compiler that supports registerized parameters, such as
  60.      * SAS/C 5.xx or greater.  
  61.      */
  62.     ULONG __asm hookEntry(
  63.         register __a0 struct Hook *h,
  64.         register __a2 VOID *o,
  65.         register __a1 VOID *msg)
  66.     {
  67.         return ((*h->h_SubEntry)(h, o, msg));
  68.     }
  69.  
  70.  
  71. Initializing a Boopsi Class
  72.  
  73. You need some simple code to initialize a class and its hook.  When initializing a class, you specify the size of the class's instance and what the superclass is, and you also have to supply a pointer to a hook entry stub.
  74.  
  75. The following code fragment illustrates the initialization of a private subclass of imageclass.
  76.  
  77.  
  78.     ULONG __saveds dispatchmyTextLabel();
  79.     
  80.     /* This is the data that each instance of our class will need. */
  81.     struct localObjData
  82.     {
  83.         /* Font to use */
  84.         struct TextFont *lod_Font;
  85.  
  86.         /* The key that is underlined */
  87.         UWORD lod_Key;
  88.  
  89.         /* DrawMode */
  90.         UBYTE lod_Mode;
  91.     };
  92.  
  93.     #define MYCLASSID           NULL
  94.     #define SUPERCLASSID        (IMAGECLASS)
  95.     #define LSIZE               (sizeof(struct localObjData))
  96.  
  97.     Class *initmyTextLabelClass (VOID)
  98.     {
  99.         extern ULONG __saveds dispatchmyTextLabel();
  100.         extern ULONG hookEntry ();        /* defined in hookface.asm */
  101.         Class *cl;
  102.  
  103.         if (cl = MakeClass (MYCLASSID, SUPERCLASSID, NULL, LSIZE, 0))
  104.         {
  105.             /* Fill in the callback hook */
  106.             cl->cl_Dispatcher.h_Entry = hookEntry;
  107.             cl->cl_Dispatcher.h_SubEntry = dispatchmyTextLabel;
  108.         }
  109.  
  110.         /* Return a pointer to the class */
  111.         return (cl);
  112.     }
  113.  
  114. In order to make the class public instead of private, do the following:
  115.  
  116.     #define MYCLASSID           "mytextlabelclass"
  117.  
  118.     ULONG __saveds dispatchmyTextLabel();
  119.  
  120.     Class *initmyTextLabelClass (VOID)
  121.     {
  122.         extern ULONG __saveds dispatchmyTextLabel();
  123.         extern ULONG hookEntry ();
  124.         Class *cl;
  125.  
  126.         if (cl = MakeClass (MYCLASSID, SUPERCLASSID, NULL, LSIZE, 0))
  127.         {
  128.             /* Fill in the callback hook */
  129.             cl->cl_Dispatcher.h_Entry = hookEntry;
  130.             cl->cl_Dispatcher.h_SubEntry = dispatchmyTextLabel;
  131.  
  132.             /* Make the class public */
  133.             AddClass (cl);
  134.         }
  135.  
  136.         /* Return a pointer to the class */
  137.         return (cl);
  138.     }
  139.  
  140.  
  141. Boopsi Dispatcher
  142.  
  143. Now all you need to do is implement a dispatcher routine.  When the dispatcher is in operation, Intuition passes method IDs to it.  The dispatcher will either execute code corresponding to the a method ID (the code is usually part of the dispatcher) or delegate processing the method to the superclass (or it can do a little of both).
  144.  
  145. The following fragment provides an example of what a dispatcher for a boopsi class looks like (Note that __saveds (Save DS) is used to insure that register A4 is set up properly for indirect addressing
  146. with the SASC compiler):
  147.  
  148.  
  149.     ULONG __saveds dispatchmyTextLabel (Class *cl, Object *o, Msg msg)
  150.     {
  151.         struct localObjData *lod;
  152.         Object *newobj;
  153.         ULONG retval;
  154.  
  155.         switch (msg->MethodID)
  156.         {
  157.             /* Create a new object */
  158.             case OM_NEW:
  159.                 /* Have our superclass create it.  DSM() passes on the message
  160.                  * to the superclass, where msg is the structure containing the
  161.                  * message specific parameters.
  162.                  */
  163.                 if (newobj = (Object *) DSM (cl, o, msg))
  164.                 {
  165.                     /* Set the attributes */
  166.                     setmyTextLabelAttrs(cl, newobj, (struct opSet *) msg);
  167.                 }
  168.  
  169.                 retval = (ULONG) newobj;
  170.                 break;
  171.  
  172.             /* Obtain information on an attribute */
  173.             case OM_GET:
  174.                 retval = getmyTextLabelAttrs (cl, o, (struct opGet *) msg);
  175.                 break;
  176.  
  177.             /* Set attributes */
  178.             case OM_UPDATE:
  179.             case OM_SET:
  180.                 /* Let the superclass set the attributes that it
  181.                  * knows about. */
  182.                 retval = DSM (cl, o, msg);
  183.  
  184.                 /* Set the attributes that we care about */
  185.                 retval |= setmyTextLabelAttrs (cl, o, (struct opSet *) msg);
  186.                 break;
  187.  
  188.             /* Draw the various states that the image supports */
  189.             case IM_DRAW:
  190.             case IM_DRAWFRAME:
  191.                 retval = drawmyTextLabel (cl, o, (struct impDraw *) msg);
  192.                 break;
  193.  
  194.             /* Let the superclass handle everything else */
  195.             default:
  196.                 retval = (ULONG) DSM (cl, o, msg);
  197.                 break;
  198.         }
  199.  
  200.         return (retval);
  201.     }
  202.  
  203.  
  204.  
  205. Boopsi Rootclass Methods
  206.  
  207. Since all classes should be subclasses of some class, with the exception of rootclass, all classes you write will be subclasses--perhaps indirectly so--of rootclass.  Because of this, your class must either implement the rootclass methods or defer processing of these methods to the superclass (as DispatchmyTextLabel() did).  Provided below are brief descriptions of the rootclass methods.  Remember that any message unrecognized by a class dispatcher should be passed to the superclass (using the amiga.lib functions DSM() or DoSuperMethod() ).
  208.  
  209. The rootclass method IDs that a subclass of imageclass needs to understand are:
  210.  
  211.     OM_NEW            Create a new object.
  212.     OM_DISPOSE        Delete an object.
  213.     OM_SET            Change an object's attributes.
  214.     OM_GET            Retrieve the value of one of the object's attributes.
  215.  
  216. The dispatcher should pass other rootclass methods on to the superclass.
  217.  
  218. Each method requires one or more parameters.  The MethodID is the only common parameter for each method.
  219.  
  220.  
  221. OM_NEW
  222.  
  223. The OM_NEW method receives the following arguments:
  224.  
  225.     struct opSet
  226.     {
  227.         ULONG MethodID;
  228.         struct TagItem *ops_AttrList;
  229.         struct GadgetInfo *ops_GInfo;
  230.     };
  231.  
  232. The ops_AttrList field contains a pointer to the TagItem array of attributes for the new object.  The ops_GInfo field is always NULL for the OM_NEW method.
  233.  
  234. Unlike other methods, this method is not passed an object pointer (since the whole idea is to create an object).  The pointer normally used to pass a boopsi object is instead used to pass the address of the object's ``true class'' (the class the object is an instance of).  That way, all class dispatchers can tell if they are the ``true class'' of the object being created (as opposed to a superclass of the true class).  Also, with this pointer, rootclass can determine what the instance data is for an object, and can allocate the right amount of memory for it.
  235.  
  236.  
  237.  
  238. For the OM_NEW method, the new class's dispatcher should do the following:
  239.  
  240. 1) Pass the message along to the superclass.  All classes do this as rootclass takes care of allocating memory for the new object.  As the OM_NEW method works ``top down'' (from rootclass down through its subclasses to the true class), each class will in turn initialize its corresponding instance data.  This all happens before the new class's dispatcher regains control.  Eventually, the message comes back from the superclass with a newly allocated object (unless of course something failed and you receive a NULL pointer instead).
  241.  
  242. 2) Obtain a pointer to the object's instance data for this class.  Use the INST_DATA() macro (defined in <intuition/classes.h>).  INST_DATA() takes two arguments, a pointer to your class and a pointer to the object.
  243.  
  244.     void *INST_DATA(*Class, *Object);
  245.  
  246. 3) Initialize your instance data.  You may allocate additional memory buffers for your object, or even create other objects which are components to objects of your class.
  247.  
  248. 4) Process your initial attribute list (from the opSet structure passed in the OM_NEW message).  In particular, process all the attributes that can be set only at initialization time.  After you deal with the ``initialization only'' attributes, apply the same attribute processing on these remaining attributes that you would apply to an OM_SET message.
  249.  
  250. 5) Return the object to the caller.
  251.  
  252.  
  253. OM_DISPOSE
  254.  
  255. The OM_DISPOSE method instructs the class to deallocate an object.  This method receives no parameters.
  256.  
  257. For the OM_DISPOSE method, the new class's dispatcher should do the following:
  258.  
  259. 1) Free any additional memory you allocated (memory allocated in step 3 from OM_NEW).
  260.  
  261. 2) Dispose of any objects that you created as components of your object (component objects created in step 3 from OM_NEW).
  262.  
  263. 3) Pass the message up to the superclass, which will eventually reach rootclass, which will free the memory allocated for the object.
  264.  
  265.  
  266.  
  267.  
  268. The mytextlabelclass example at the end of this article does not allocate any extra resources when it creates an object.  Because it does not have to release any resources, the mytextlabelclass dispatcher lets its superclass handle the OM_DISPOSE method.  Eventually, some superclass of mytextlabelclass will deallocate all of the memory for the OM_DISPOSEd object.
  269.  
  270.  
  271. OM_SET
  272.  
  273. This method is used to set an object's attributes.  The Intuition function SetAttr() calls this method.  It receives the following arguments:
  274.  
  275.     struct opSet
  276.     {
  277.         ULONG MethodID;
  278.         struct TagItem *ops_AttrList;
  279.         struct GadgetInfo *ops_GInfo;
  280.     };
  281.  
  282.  
  283. For the OM_SET method, the new class's dispatcher should process the attributes your class recognizes and have the superclass process any unrecognized attributes.  Note that a subclass dispatcher can directly process attributes it inherits from a superclass, rather than passing the message on to the superclass.  
  284.  
  285. Note that mytextlabelclass treats the OM_UPDATE method exactly like the OM_SET method.  This is OK because these two methods are functionally equivalent for imageclass classes.
  286.  
  287.  
  288. OM_GET
  289.  
  290. Retrieve an object's attribute.  This method receives the following parameters:
  291.  
  292.     struct opGet
  293.     {
  294.         ULONG MethodID;
  295.         ULONG opg_AttrID;
  296.         ULONG *opg_Storage;
  297.     };
  298.  
  299. If the new class recognizes the attribute, the new class should fill in opg_Storage's target with the attribute's value.  If the attribute is actually the attribute of some component object, you might want to pass the message on and let the component object process the OM_GET.  If completely unrecognized, you should pass the message to your superclass.  
  300.  
  301.  
  302.  
  303. Imageclass Methods
  304.  
  305. Imageclass defines several methods of its own which subclasses of imageclass either have to implement or pass on to their superclass.  The method IDs for imageclass are defined in <intuition/imageclass.h>.  Each method requires some parameters.  The MethodID is the only parameter common to each method.
  306.  
  307.  
  308. IM_DRAW
  309.  
  310. The IM_DRAW method is used to tell the image to render itself.  The Intuition function DrawImageState() uses this method.  IM_DRAW receives the following parameters:
  311.  
  312.     struct impDraw
  313.     {
  314.         ULONG MethodID;
  315.         struct RastPort *imp_RPort;
  316.         struct
  317.         {
  318.             WORD X;
  319.             WORD Y;
  320.         } imp_Offset;
  321.  
  322.         ULONG imp_State;
  323.         struct DrawInfo *imp_DrInfo;
  324.     };
  325.  
  326. The imp_State field contains the visual state to render the image.  The visual states (defined in <intuition/imageclass.h>) are:
  327.  
  328.     IDS_NORMAL            idle state
  329.     IDS_SELECTED            for selected gadgets.
  330.     IDS_DISABLED            for disabled gadgets.
  331.     IDS_BUSY            for future functionality
  332.     IDS_INDETERMINATE        for future functionality
  333.     IDS_INACTIVENORMAL        normal, in inactive window border.
  334.     IDS_INACTIVESELECTED    selected, in inactive border.
  335.     IDS_INACTIVEDISABLED    disabled, in inactive border.
  336.  
  337. When setting the pens to render an image, use the values from the imp_DrInfo->dri_Pens pen array (Note that it is possible that imp_DrInfo will be NULL).  The possible pen values are defined in <intuition/screens.h>.
  338.  
  339. The following code fragment shows how to use the shadow color for rendering.
  340.  
  341.     UWORD *pens = (imp->imp_DrInfo) ? imp->imp_DrInfo->dri_Pens : NULL;
  342.  
  343.     if (pens)
  344.     {
  345.         SetAPen (imp->imp_RPort, pens[SHADOWPEN]);
  346.     }
  347. IM_ERASE
  348.  
  349. The IM_ERASE method tells an image to erase itself.  The Intuition function EraseImage() uses this method.  IM_ERASE receives the following parameters:
  350.  
  351.     struct impErase
  352.     {
  353.         ULONG MethodID;
  354.         struct RastPort *imp_RPort;
  355.         struct
  356.         {
  357.             WORD X;
  358.             WORD Y;
  359.         } imp_Offset;
  360.     };
  361.  
  362. The mytextlabelclass example doesn't know anything about this method, so it blindly passes this message on to the superclass.  The superclass, imageclass, will call the graphics.library function EraseRect() using the dimensions found in the imageclass object's Image structure.
  363.  
  364.  
  365. IM_HITTEST
  366.  
  367. IM_HITTEST returns true if a point is within the image.  The Intuition function PointInImage() uses this method.  IM_HITTEST requires the following parameters:
  368.  
  369.     struct impHitTest
  370.     {
  371.         ULONG MethodID;
  372.         struct
  373.         {
  374.             WORD X;
  375.             WORD Y;
  376.         } imp_Point;
  377.     };
  378.  
  379. The mytextlabelclass example blindly passes this method on to its superclass.  The superclass, imageclass, will return TRUE if the point is within the old Image structure box.
  380.  
  381.  
  382. IM_DRAWFRAME
  383.  
  384. The IM_DRAWFRAME method instructs the image to render itself within the confines of the given rectangle.  It receives the following parameters:
  385.  
  386.     struct impDraw
  387.     {
  388.         ULONG MethodID;
  389.         struct RastPort *imp_RPort;
  390.         struct
  391.         {
  392.             WORD X;
  393.             WORD Y;
  394.         } imp_Offset;
  395.  
  396.         ULONG imp_State;
  397.         struct DrawInfo *imp_DrInfo;
  398.  
  399.         struct
  400.         {
  401.             WORD Width;
  402.             WORD Height;
  403.         } imp_Dimensions;
  404.     };
  405. The Width and Height fields provide the object's rectangular bounds.  How the image object deals with the frame is implementation specific.  Typically, a scaleable image will scale itself as best it can to fit into the rectangle.  The mytextlabelclass.c example does not actually implement this method, instead mytextlabelclass treats IM_DRAWFRAME like the IM_DRAW method.
  406.  
  407. In general, applications that use this method to draw an object should use the IM_ERASEFRAME method (see below) to erase it.  This will ensure that the image was erased at the proper scale. 
  408.  
  409.  
  410. IM_ERASEFRAME
  411.  
  412. The IM_ERASEFRAME method instructs an image confined to a given rectangle to erase itself.  Normally this method is used to erase an image drawn using the IM_DRAWFRAME method.  This method expects the following parameters:
  413.  
  414.     struct impErase
  415.     {
  416.         ULONG MethodID;
  417.         struct RastPort *imp_RPort;
  418.         struct
  419.         {
  420.             WORD X;
  421.             WORD Y;
  422.         } imp_Offset;
  423.  
  424.         /* these parameters only valid for IM_ERASEFRAME */
  425.         struct
  426.         {
  427.             WORD Width;
  428.             WORD Height;
  429.         } imp_Dimensions;
  430.     };
  431.  
  432. The mytextlabelclass example blindly passes this method on to its superclass.  The superclass treats IM_ERASEFRAME just like IM_ERASE.
  433.  
  434.  
  435. IM_HITFRAME
  436.  
  437. The IM_HITFRAME method is used to determine if a point is within an image that is contained within (or scaled to) the given rectangle.  This method is intended to test images that were rendered using IM_DRAWFRAME.  This method receives the following parameters:
  438.  
  439.     struct impHitTest
  440.     {
  441.         ULONG MethodID;
  442.         struct
  443.         {
  444.             WORD X;
  445.             WORD Y;
  446.         } imp_Point;
  447.  
  448.         struct
  449.         {
  450.             WORD Width;
  451.             WORD Height;
  452.         } imp_Dimensions;
  453.     };
  454.  
  455. The mytextlabelclass example blindly passes this method on to its superclass.  The superclass treat this meothd just like the IM_HITTEST method.
  456.  
  457.  
  458. IM_MOVE
  459.  
  460. The IM_MOVE method erases and redraws an image.  It is intended for use in some subclass of imageclass that performs animation or smooth dragging.  Currently, no public boopsi classes implement this method.
  461.  
  462.  
  463. IM_FRAMEBOX
  464.  
  465. The IM_FRAMEBOX method returns size information for an image (usually some sort of frame image).  The following parameters are associated with the IM_FRAMEBOX method.
  466.  
  467.     struct impFrameBox
  468.     {
  469.         ULONG MethodID;
  470.         struct IBox *imp_ContentsBox;  /* Application supplied IBox for the result */
  471.         struct IBox *imp_FrameBox;     /* Rectangle to frame */
  472.         struct DrawInfo *imp_DrInfo;
  473.         ULONG imp_FrameFlags;
  474.     };
  475.  
  476. This method is used to ask the image what size it would like to be, if it had to frame the rectangle in the imp_FrameBox field.  This method normally applies only to image classes that put a frame around some object (like frameiclass).  By passing the dimensions and position of a rectangle, the framing image determines the position and size it should be to properly ``frame'' the object bounded by the imp_FrameBox rectangle.  IM_FRAMEBOX stores the result in the IBox structure pointed to by imp_ContentsBox.  This method allows an application to use a framing image without having to worry about image specific details such as accounting for the thickness of the frame or centering the frame around the object.
  477.  
  478. The imp_FrameFlags field is a bit field used to specify certain options for the IM_FRAMEBOX method.  Currently, there is only one defined for it, FRAMEF_SPECIFY.  If this bit is set, IM_FRAMEBOX has to use the width and height supplied to it in the imp_FrameBox field (even if these are too small!) as the frame dimensions.  It can only adjust its position, typically to center the object as best as possible.  
  479.  
  480. This method is not supported by the mytextlabelclass example.  It passes this message to its superclass which does not support this method either.  When the message returns from the superclass, the return value will be zero, indicating to the application that this method is not supported.
  481.  
  482.  
  483.  
  484.  
  485.  
  486.  
  487. Image Class Example
  488.  
  489. The image class example code, mytextlabelclass.c, illustrates a complete custom image class.  This image class provides an application with textual labels that have a particular character underlined.  This is useful for indicating which key controls a gadget (although the example provided only utilizes imageclass objects; there are no gadgets involved).
  490.  
  491. A custom image can be used in the place of any standard Intuition Image structure.  For example, an application can attach an imageclass object to: the GadgetRender and SelectRender fields of a Gadget structure (defined in <intuition/intuition.h>), the ReqImage field of a Requester structure, or even the ItemFill field of the MenuItem structure.
  492.  
  493. Under Intuition V36, the DrawImage() function passed an invalid DrawInfo structure, therefore it wasn't possible to use a custom imageclass object and the DrawImage() together.  With V37, a NULL DrawInfo is passed when no valid DrawInfo is available.
  494.  
  495. The example code (usemyIC.c) initializes and uses a custom imageclass object.  Notice that usemyIC.c directly manipulates fields within the Image structure embedded within the boopsi imageclass object.  This is legal for image classes whose immediate superclass is imageclass (for the LeftEdge, TopEdge, Width, Height, ImageData, PlanePick, and PlaneOnOff Image structure fields only; the other Image structure fields are off limits).  Indirect subclasses of imageclass may not alter the values in the embedded Image structure as future direct subclasses of imageclass may need to know about changes to values in the Image structure.
  496.  
  497.